////////////////////////////////////////////////////////////////////////////////
//
//  The MIT License
//
//  Copyright (c) 2008 - 2009. Dimarik
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
//
////////////////////////////////////////////////////////////////////////////////

package ru.goodcore.logging.targets {

import com.bit101.components.PushButton;
import com.bit101.components.Slider;

import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.display.GradientType;
import flash.display.InteractiveObject;
import flash.display.Shape;
import flash.display.Sprite;
import flash.display.Stage;
import flash.events.Event;
import flash.events.EventPhase;
import flash.events.MouseEvent;
import flash.geom.Matrix;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.text.TextFieldType;
import flash.text.TextFormat;

import ru.goodcore.logging.ILogger;
import ru.goodcore.logging.LogEvent;
import ru.goodcore.logging.LogEventLevel;
import ru.goodcore.logging.formatters.HTMLFormatter;
import ru.goodcore.logging.formatters.ITargetMessageFormatter;

/**
 * Simple built-in debug target. Creates a pseudo Output panel in a target swf
 * for displaying log statements.
 *
 *
 * @author					Dimarik
 * @version					1.0
 * @playerversion			Flash 9
 * @langversion				3.0
 *
 * @keyword					loggingtarget, debugtarget, target, builtin
 */
public class BuiltInDebugTarget extends Sprite implements ILoggingTarget {

	//-------------------------------------------------------------------------
	//
	//  Constructor
	//
	//-------------------------------------------------------------------------

	/**
	 * Constructor
	 */
	public function BuiltInDebugTarget() {
		super();
		this.initialize();
	}

	//-------------------------------------------------------------------------
	//
	//  Properties
	//
	//-------------------------------------------------------------------------

	//---------------------------------
	//  autoExpand
	//---------------------------------
	[Inspectable(category="General", defaultValue="false")]
	/**
	 * @private
	 */
	private var _autoExpand:	Boolean = false;

	public function set autoExpand(value:Boolean):void {
		if (this._autoExpand == value) return;
		this._autoExpand = value;
	}

	public function get autoExpand():Boolean {
		return this._autoExpand;
	}


	//---------------------------------
	//  maxLength
	//---------------------------------
	[Inspectable(category="General", type="Number", defaultValue="10000")]
	/**
	 * @private 
	 */
	private var _bufferSize:		uint = 1e4;

	public function set bufferSize(value:uint):void {
		if (this._bufferSize == value) return;
		this._bufferSize = value;
	}

	public function get bufferSize():uint {
		return this._bufferSize;
	}

	//-------------------------------------------------------------------------
	//
	//  Properties. Interface implementation: ILoggingTarget
	//
	//-------------------------------------------------------------------------

	//---------------------------------
	//  contentFilters
	//---------------------------------
	/**
	 * @inheritDoc
	 * @see  ru.goodcore.logging.targets.ILoggingTarget#contentFilters
	 */
	public function get contentFilters():Array/* of String */ {
		return ['*'];
	}

	/**
	 * @private
	 */
	public function set contentFilters(value:Array/* of String */):void {
		
	}

	//---------------------------------
	//  level
	//---------------------------------
	private var _level:int = LogEventLevel.ALL;
	
	
	/**
	 * @inheritDoc
	 * @see  ru.goodcore.logging.targets.ILoggingTarget#level
	 */	
	public function get level():int {
		return this._level;
	}
	
	/**
	 * @private
	 */
	public function set level(value:int):void {
		if (value < 0 || this._level == value) return;
		this._level = value; 
	}

	//----------------------------------
	//  formatter
	//----------------------------------
	/**
	 * @private 
	 */
	private var _formatter:		ITargetMessageFormatter;

	/**
	 * @private
	 */
	public function get formatter():ITargetMessageFormatter {
		if ( !this._formatter ) {
			this._formatter = new HTMLFormatter();
		}
		return this._formatter;
	}

	public function set formatter(value:ITargetMessageFormatter):void {
		this._formatter = value;
	}



	//-------------------------------------------------------------------------
	//
	//  Private variables
	//
	//-------------------------------------------------------------------------

	/**
	 * @private 
	 */
	private var _txt:			TextField;

	/**
	 * @private 
	 */
	private var _menu:			Sprite;

	/**
	 * @private 
	 */
	private var _titleBar:		Sprite;

	/**
	 * @private 
	 */
	private var _label:			TextField;

	/**
	 * @private 
	 */
	private var _slider:		Slider;

	/**
	 * @private 
	 */
	private var _focus:			InteractiveObject;

	/**
	 * @private 
	 */
	private var _stage:			Stage;

	/**
	 * @private 
	 */
	private var _hasError:		Boolean = false;

	//-------------------------------------------------------------------------
	//
	//  Public methods. Interface implementation: ILoggingTarget
	//
	//-------------------------------------------------------------------------

	/**
	 * @see  ru.goodcore.logging.targets.ILoggingTarget#addLogger
	 * @param logger
	 */
	public function addLogger(logger:ILogger):void {
		if (logger)
			logger.addEventListener(LogEvent.LOG, this.handler_log);
	}

	/**
	 * @see  ru.goodcore.logging.targets.ILoggingTarget#removeLogger 
	 * @param logger
	 */
	public function removeLogger(logger:ILogger):void {
		if (logger)
			logger.removeEventListener(LogEvent.LOG, this.handler_log);
	}

	//-------------------------------------------------------------------------
	//
	//  Public methods
	//
	//-------------------------------------------------------------------------

	/**
	 * Clears the output console
	 */
	public function clear():void {
		this._txt.text = '';
		this._txt.htmlText = '';
	}

	/**
	 * Open the output console
	 */
	public function open(event:Event = null):void {
		this.toggleCollapse(null);
	}

	/**
	 * Close the output console
	 */
	public function close(event:Event = null):void {
		this.toggleCollapse(null);
	}

	//-------------------------------------------------------------------------
	//
	//  Private methods
	//
	//-------------------------------------------------------------------------

	/**
	 * @private
	 */	
	private function initialize():void {
		this._titleBar = this.createTitleBar();
		this._titleBar.doubleClickEnabled = true;
		super.addChild(this._titleBar);
		
		this._menu = this.createMenu();
		this._menu.y = this._titleBar.height;

		this._txt = this.createOutputField();
		this._txt.y = this._titleBar.height + this._menu.height;

		this._slider = new Slider(Slider.VERTICAL, null, 0, 0, this.handler_slider);
		this._slider.backClick = true;
		this._slider.maximum = 1;
		this._slider.y = this._txt.y;

		super.addEventListener(Event.ADDED_TO_STAGE,	this.handler_addedToStage);
		super.addEventListener(Event.ADDED,				this.handler_added);
		super.addEventListener(Event.REMOVED,			this.handler_removed);
	}

	/**
	 * @private
	 * @param event 
	 */
	private function logEvent(event:LogEvent):void {

		if (event.level >= LogEventLevel.ERROR) {
			if ( !this._hasError ) {
				this._hasError = true;
				var format:TextFormat = this._label.getTextFormat();
				format.color = 0xC31101;
				format.bold = true;
				this._label.setTextFormat(format);
			}
		}

		var stage:DisplayObjectContainer = super.stage;
		if (
			stage && 
			stage.getChildIndex(this) != stage.numChildren - 1
		) {
			//correct depth
			stage.setChildIndex(this, stage.numChildren - 1);
		}

		var formattedMessage:String = this.formatter.formatMessage(event);

		this._txt.htmlText += formattedMessage;

		if ( this._txt.length > this._bufferSize)
			this._txt.text = this._txt.text.slice(-this._bufferSize);

		this._txt.scrollV = this._txt.maxScrollV;

		this._slider.minimum = this._txt.maxScrollV;

		if (this._autoExpand && !this._txt.parent) 
				this.toggleCollapse();
	}


	/**
	 * @private
	 */
	private function createOutputField():TextField {
		var textField:TextField = new TextField();
		textField.type = TextFieldType.DYNAMIC;
		//textField.type = TextFieldType.INPUT;
		//textField.opaqueBackground = 0x1F3DA3;
		textField.opaqueBackground = 0x00006F;
		textField.height = 30;
		textField.multiline = true;

		var format:TextFormat = textField.getTextFormat();
		//format.font = '_typewriter';
		format.color = 0xFFFFFF;
		format.size = 12;
		textField.setTextFormat(format);
		textField.defaultTextFormat = format;
		return textField;
	}

	/**
	 * @private
	 */
	private function createTitleBar():Sprite {
		var barGraphics:Shape = new Shape();

		var colors:Array = new Array(0xE0E0F0, 0xB0C0D0, 0xE0E0F0);
		var alphas:Array = new Array(1, 1, 1);
		var ratios:Array = new Array(0, 50, 255);
		var gradientMatrix:Matrix = new Matrix();
		gradientMatrix.createGradientBox(18, 18, Math.PI/2, 0, 0);

		with (barGraphics.graphics) {
			beginGradientFill(GradientType.LINEAR, colors, alphas, ratios, gradientMatrix);
			drawRoundRectComplex(0, 0, 65, 16, 4, 0, 0, 0);
			endFill();
		}

		var barLabel:TextField = 
		this._label = new TextField();
		barLabel.autoSize = TextFieldAutoSize.LEFT;
		barLabel.selectable = false;
		barLabel.text = 'Log window';
		var format:TextFormat = barLabel.getTextFormat();
		format.font = '_sans';
		format.size = 9;
		barLabel.setTextFormat(format);
		barLabel.x = 3;

		var titleBar:Sprite = new Sprite();
		titleBar.addChild(barGraphics);
		titleBar.addChild(barLabel);
		return titleBar;
	}

	/**
	 * @private
	 */
	private function createMenu():Sprite {
		var base:Sprite = new Sprite();

		var barGraphics:Shape = new Shape();

		with (barGraphics.graphics) {		
			beginFill(0xB0C0D0);
			drawRect(0, 0, 10, 24);
			endFill();
		}

		base.addChild(barGraphics);
		var button:PushButton = new PushButton(base, 2, 2, "Clear", this.handler_clickClearButton);
		button.label
		button.width = 50;
		return base;
	}
	
	/**
	 * @private
	 */
	private function resize():void {
		if ( !super.stage ) return;

		if (super.parent != super.stage) {
			super.stage.addChild(this);
		}

		var parentWidth:	Number = super.stage.stageWidth;
		var parentHeight:	Number = super.stage.stageHeight;

		this._txt.width = parentWidth;
		this._txt.height = parentHeight - this._titleBar.height - this._menu.height;

		this._titleBar.x = parentWidth - this._titleBar.width;
		this._titleBar.y = this._txt.parent ? 
				0 : 
				parentHeight - this._titleBar.height;

		var menuBackground:DisplayObject = this._menu.getChildAt(0);
		menuBackground.width = parentWidth;

		this._slider.x = this._txt.x + this._txt.width - this._slider.width;
		this._slider.height = this._txt.height;
	}

	//-------------------------------------------------------------------------
	//
	//  Event handlers
	//
	//-------------------------------------------------------------------------

	/**
	 * @private
	 */
	private function handler_addedToStage(event:Event):void {
		//trace('handler_addedToStage: ' + this._stage);
		this._stage = super.stage;

		super.removeEventListener(event.type,				this.handler_addedToStage);

		super.stage.addEventListener(Event.RESIZE,			this.handler_resize);

		super.addEventListener(Event.REMOVED_FROM_STAGE,			this.handler_removedFromStage);
		this._titleBar.addEventListener(MouseEvent.CLICK,			this.toggleCollapse);

		this.resize();
	}

	/**
	 * @private
	 */
	private function handler_removedFromStage(event:Event):void {
		//trace('handler_removedFromStage: ' + this._stage);

		super.removeEventListener(event.type,					this.handler_removedFromStage);
		super.stage.removeEventListener(Event.RESIZE,			this.handler_resize);
		this._titleBar.removeEventListener(MouseEvent.CLICK,	this.toggleCollapse);

		super.addEventListener(Event.ADDED_TO_STAGE,			this.handler_addedToStage);

		super.addEventListener(Event.EXIT_FRAME,				this.handler_exitFrame);
	}
	
	private function handler_exitFrame(event:Event):void {
		super.removeEventListener(event.type, arguments.callee);
		if (this._stage)
			this._stage.addChild(this);
	}

	/**
	 * @private
	 */
	private function handler_added(event:Event):void {
		//trace('handler_added: ' + this._stage);
		return;
		if (event.eventPhase == EventPhase.AT_TARGET) {
			super.parent.addEventListener(Event.ADDED, this.handler_adedToParent);
		} 
	}

	/**
	 * @private
	 */
	private function handler_adedToParent(event:Event):void {
		if (event.target.parent == super.parent) {
			super.parent.setChildIndex(this, super.parent.numChildren - 1);
			this.resize();
		}
	}


	/**
	 * @private
	 */

	private function handler_removed(event:Event):void {
		//trace('removed: ' + event.target);
	}


	/**
	 * @private
	 */
	private function handler_clickClearButton(event:MouseEvent):void {
		this.clear();
	}

	/**
	 * @private
	 */
	private function toggleCollapse(event:Event = null):void {
		if (!this._txt.parent) {
			this._focus = super.stage.focus;
			super.addChild(this._txt);
			this._txt.addEventListener(Event.SCROLL, this.handler_scrollTextField);
			super.addChild(this._slider);
			super.addChild(this._menu);
			this._txt.dispatchEvent( new Event(Event.SCROLL) );
		} else {
			super.stage.focus = this._focus; // Restore focus on child
			super.removeChild(this._txt);
			super.removeChild(this._menu);
			this._txt.removeEventListener(Event.SCROLL, this.handler_scrollTextField);
			super.removeChild(this._slider);
			
		}
		this.handler_resize(event);
	}

	/**
	 * @private
	 */
	private function handler_resize(event:Event):void {
		this.resize();
	}

	/**
	 *  @private
	 *  This method will call the <code>logEvent</code> method if the level of the
	 *  event is appropriate for the current level.
	 */
	private function handler_log(event:LogEvent):void {
		if (event.level >= this.level)
			this.logEvent(event);
	}

	/**
	 *  @private
	 */
	private function handler_slider(event:Event):void {
		this._txt.removeEventListener(Event.SCROLL, this.handler_scrollTextField);
		this._txt.scrollV = this._slider.value;
		this._txt.addEventListener(Event.SCROLL, this.handler_scrollTextField);
	}

	/**
	 *  @private
	 */
	private function handler_scrollTextField(event:Event):void {
		var max:	uint = this._txt.maxScrollV;
		this._slider.visible = max != 1;
		this._slider.minimum = max;

		this._slider.value = this._txt.scrollV;
	}

}
}